package com.dronff.gvm.memorymanager.impl;

import com.dronff.gvm.interfaces.*;
import com.dronff.gvm.memorymanager.Memory;
import com.dronff.gvm.memorymanager.SafeBytes;

import java.util.*;

/**
 * @author: tyf
 * @date: 2022/9/9 10:04
 * @description: todo
 */
public class AllocatableMemoryImpl extends Memory implements AllocatableMemory {
    private static Map<Integer,AllocatableMemoryImpl> memories = new HashMap<>();
    private AllocatableMemoryImpl(){
        super();
    }
    private AllocatableMemoryImpl(AllocatableMemory parent , int size){
        super(parent,size,0);
    }
    public static AllocatableMemoryImpl newInstance(int size){
        AllocatableMemoryImpl allocatableMemory = new AllocatableMemoryImpl(null, size);
        memories.put(allocatableMemory.getHash(),allocatableMemory);
        return allocatableMemory;
    }

    @Override
    public MiniMemoryImpl alloc(int size) throws OutOfMemoryError, IllegalAccessException {
        if(getCurrentIndex() + size > getSize()){
            throw new OutOfMemoryError("fail to allo memory:out of memory!");
        }
        MiniMemoryImpl.MiniMemoryBuilder builder = MiniMemoryImpl.getBuilder();
        MiniMemoryImpl miniMemory = builder
                .setSize(size)
                .setParent(this)
                .setOffset(getCurrentIndex())
                .build();
        setCurrentIndex(getCurrentIndex() + size);
        return miniMemory;
    }

    @Override
    public AllocatableMemoryImpl allocRegion(int size) throws OutOfMemoryError, IllegalAccessException {
        if(getCurrentIndex() + size > getSize()){
            throw new OutOfMemoryError("fail to alloc region memory:out of memory!");
        }
        AlloctableMemoryBuilder builder = getBuilder();
        AllocatableMemoryImpl miniMemory = builder
                .setSize(size)
                .setParent(this)
                .setOffset(getCurrentIndex())
                .build();
        setCurrentIndex(getCurrentIndex() + size);
        return miniMemory;
    }

    @Override
    public <T> T readOnly(ReadFunction<T> function) {
        assert function != null;
        if(getMemory() == null && getParent() == null){
            throw new IndexOutOfBoundsException("memory and parent both equals 'NULL'");
        }
        // 如果有父内存 调用父内存的readOnlyChild方法
        if(getParent() != null){
            return getParent().readOnlyChild(function,this);
        }
        return function.read(getMemory());
    }

    @Override
    public <T> T readOnlyChild(ReadFunction<T> function, Memory memory) {
        if(getMemory() == null && getParent() == null){
            throw new IndexOutOfBoundsException("memory and parent both equals 'NULL'");
        }
        if(getParent() != null){
            return getParent().readOnlyChild(function,memory);
        }
        System.out.println("size"+getSize());
        return function.read(Arrays.copyOfRange(getMemory(),memory.getOffset(), memory.getOffset()+memory.getSize()));
    }

    @Override
    public <W> void writeOnly(WriteFunction<W> function) {
        assert function != null;
        if(getMemory() == null && getParent() == null){
            throw new IndexOutOfBoundsException("memory and parent both equals 'NULL'");
        }
        if(getParent() != null){
            getParent().writeOnlyChild(function,this);
            return;
        }
        function.write((W) getMemory());
    }

    @Override
    public <W> void writeOnlyChild(WriteFunction<W> function, Memory memory) {
        if(getMemory() == null && getParent() == null){
            throw new IndexOutOfBoundsException("memory and parent both equals 'NULL'");
        }
        if(getParent() != null){
            getParent().writeOnlyChild(function,memory);
            return;
        }
        /**
         * 给他安全的Bytes
         */
        function.write((W) new SafeBytes(getMemory(),memory));
    }
    public static AlloctableMemoryBuilder getBuilder(){
        return new AlloctableMemoryBuilder();
    }
    public static class AlloctableMemoryBuilder implements AutoGenerate{
        private AllocatableMemoryImpl allocatableMemory;
        private AlloctableMemoryBuilder(){
            this.allocatableMemory = new AllocatableMemoryImpl();
        }
        public AlloctableMemoryBuilder setSize(int size){
            allocatableMemory.setSize(size);
            return this;
        }
        public AlloctableMemoryBuilder setParent(AllocatableMemory parent) throws IllegalAccessException {
            allocatableMemory.setParent(parent);
            return this;
        }
        public AlloctableMemoryBuilder setOffset(int offset){
            allocatableMemory.setOffset(offset);
            return this;
        }
        public AllocatableMemoryImpl build(){
            return allocatableMemory;
        }
    }
    public static AllocatableMemoryImpl getAllocatableMemory(int hash){
        return memories.get(hash);
    }
}
